/***************************************************************************
 *   Copyright (C) 2007, 2008, 2010 by Lasse Liehu                         *
 *   lliehu@kolumbus.fi                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "unreaditemscheck.h"

#include <QHttp>
#include <QByteArray>
#include <QBuffer>
#include <QHttpRequestHeader>
#include <QHttpResponseHeader>
#include <QUrl>
#include <QDomDocument>
#include <QDomElement>
#include <QTimer>
#include <QtDebug>

#include "config.h"
#include "googlelogindata.h"

//BEGIN UnreadItemsCheckPrivate
class UnreadItemsCheckPrivate
{
    public:
        UnreadItemsCheckPrivate(GoogleLoginData& loginData_)
        : loginData(loginData_)
        {
        }

        GoogleLoginData& loginData;

        QHttp *http;
        QBuffer *buffer;
        int httpReqId;
        QByteArray receivedData;
        // this is true if the error in request has already been noted and
        // handled and signal checkCompleted has been emitted
        bool requestErrorHandledAlready;
};
//END UnreadItemsCheckPrivate

class UnreadItemsCheckXmlException {};

UnreadItemsCheck::UnreadItemsCheck(GoogleLoginData& loginData, QObject *parent)
    : QObject(parent), d(new UnreadItemsCheckPrivate(loginData))
{
    d->http = 0;
    d->buffer = 0;
    d->httpReqId = 0;
    d->requestErrorHandledAlready = false;
}

UnreadItemsCheck::~UnreadItemsCheck()
{
    delete d;
}

void UnreadItemsCheck::start()
{
    QTimer::singleShot(0, this, SLOT(sendRequest()));
}

void UnreadItemsCheck::sendRequest()
{
    // NOTE we don't check if we have a valid SID or not, it's the manager's job
    //      i.e. we should _always_ have a valid SID when we get here

    qDebug() << "greader(api)/unread-count: An unread count check started.";

    if ( d->loginData.sid().isEmpty() )
    {
        qWarning() << "greader(api)/unread-count: warn: operation was started, "
            "but SID was invalid";
        emit checkCompleted(false, "");
        return;
    }

    d->http = new QHttp(this);
    connect(d->http, SIGNAL(requestFinished(int, bool) ), this,
        SLOT(slotHttpRequestFinished(int, bool) ));
    connect(d->http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &) ), this,
        SLOT(readResponseHeader(const QHttpResponseHeader &) ));
    d->buffer = new QBuffer;
    d->buffer->open(QBuffer::ReadWrite);
    QString urlStr("https://www.google.com/reader/api/0/unread-count?all=true");
    QUrl url(urlStr);
    d->http->setHost(url.host(), QHttp::ConnectionModeHttp);
    QHttpRequestHeader header("GET", urlStr);
    header.addValue("Cookie", QString("SID=%1").arg(d->loginData.sid() ) );
    d->httpReqId = d->http->request(header, QByteArray(), d->buffer);
#ifdef VERBOSE_NETWORK_DATA_DEBUG
    qDebug() << "greader(api)/unread-count: Request is: "
             << header.toString();
#endif
}

void UnreadItemsCheck::handleData()
{
#ifdef VERBOSE_NETWORK_DATA_DEBUG
    qDebug() << "greader(api)/unread-count: Received data: " << d->receivedData;
#endif

    QMap<QString, int> unreadCounts;
    try
    {
        QDomDocument xmlDoc;
        if (! xmlDoc.setContent(QString::fromUtf8(d->receivedData) ))
        {
            emit checkCompleted(false, tr("Got invalid data from Google Reader.") );
            return;
        }

        QDomNodeList tmpNodeList;

        QDomElement rootElem = xmlDoc.documentElement();
        // the root element's name must be "object"
        if (rootElem.tagName() != "object") throw UnreadItemsCheckXmlException();
        tmpNodeList = rootElem.elementsByTagName("list");
        // the root object must have _exactly_ one child element, named "list"
        if (tmpNodeList.length() != 1) throw UnreadItemsCheckXmlException();
        QDomElement listElem = tmpNodeList.at(0).toElement();
        // the tag "list" must have "name" attr with value "unreadcounts"
        if (listElem.isNull() || listElem.attribute("name") != "unreadcounts")
        {
            throw UnreadItemsCheckXmlException();
        }

        // parsing all "object" elements
        tmpNodeList = listElem.elementsByTagName("object");
        for (int i = 0; i < tmpNodeList.size(); ++i)
        {
            QDomElement elem = tmpNodeList.at(i).toElement();
            if (elem.isNull() ) throw UnreadItemsCheckXmlException();
            QDomNodeList tmpNodeList2 = elem.elementsByTagName("string");
            if (tmpNodeList2.size() != 1) throw UnreadItemsCheckXmlException();
            QDomElement strElem = tmpNodeList2.at(0).toElement();
            if (strElem.isNull() ) throw UnreadItemsCheckXmlException();
            QRegExp labelRegEx = QRegExp("user\\/\\d+\\/(label\\/.+)");
            QRegExp feedRegEx = QRegExp("(feed\\/.+)");
            QRegExp comGoogleRegEx = QRegExp("user\\/\\d+\\/state\\/(com\\.google\\/.+)");
            bool labelFound = false;
            bool feedFound = false;
            bool comGoogleFound = false;
            if ( (labelFound = strElem.text().contains(labelRegEx)) ||
                    (feedFound = strElem.text().contains(feedRegEx)) ||
                    (comGoogleFound = strElem.text().contains(comGoogleRegEx)) )
            {
                QDomNodeList numberNodes = elem.elementsByTagName("number");
                for (int i = 0; i < numberNodes.size(); ++i)
                {
                    if (!numberNodes.at(i).isElement() ) continue;
                    QDomElement numberElem = numberNodes.at(i).toElement();
                    if (numberElem.attribute("name") == "count")
                    {
                        // The identifier is one of these:
                        // label/[label]
                        // feed/[feed url]
                        // com.google/reading-list
                        // com.google/broadcast-friends
                        // com.google/broadcast
                        QString identifier;
                        if (labelFound) identifier = labelRegEx.cap(1);
                        else if (feedFound) identifier = feedRegEx.cap(1);
                        else if (comGoogleFound) identifier = comGoogleRegEx.cap(1);
                        if (comGoogleFound && (identifier != "com.google/reading-list" &&
                            identifier != "com.google/broadcast-friends" &&
                            identifier != "com.google/broadcast") )
                        {
                            notifyAboutUnsupportedIdentifier(
                                QString("/user/[...]/state/") + identifier);
                        }
                        int unreadCount = numberElem.text().toInt();
                        if (unreadCount != 0 && (!identifier.isEmpty() ) )
                        {
                            unreadCounts.insert(identifier, unreadCount );
                            continue;
                        }
                    }
                    else if (numberElem.attribute("name") == "newestItemTimestampUsec") continue;
                    // if we're here, then an error occurred
                    throw UnreadItemsCheckXmlException();
                }
            }
            else
            {
                notifyAboutUnsupportedIdentifier(strElem.text() );
            }
        }
    }
    catch (UnreadItemsCheckXmlException& e)
    {
        emit checkCompleted(false, tr("Got invalid data from Google Reader.") );
        return;
    }
    // Calculating the total amount of unread items by adding the counts of all feeds together
    QMapIterator<QString, int> unreadCountsIter(unreadCounts);
    int unreadCountTotal = 0;
    while (unreadCountsIter.hasNext() )
    {
        unreadCountsIter.next();
        if (unreadCountsIter.key().contains("feed/") )
        {
            unreadCountTotal += unreadCountsIter.value();
        }
    }
    unreadCountTotal += unreadCounts.value("com.google/broadcast", 0 /*value returned if it wasn't found*/);
    if ( ! unreadCounts.isEmpty() )
    {
        unreadCounts.insert("system/total", unreadCountTotal);
    }

    // Finishing it!
    emit checkCompleted(true, "");
    emit unreadItemsCountUpdated(unreadCounts);
}

void UnreadItemsCheck::slotHttpRequestFinished(int reqId, bool error)
{
    if (reqId != d->httpReqId) return;

    if (error)
    {
        qDebug() << "greader(api)/unread-count: Couldn't get the unread count data";
        d->buffer->close();
        delete d->buffer;
        d->buffer = 0;

        // if we didn't get a reply at all
        if ( ! d->http->lastResponse().isValid() )
        {
            emit checkCompleted(false, tr("Couldn't connect to Google Reader.") );
        }
        // if we haven't handled this error already
        else if ( ! d->requestErrorHandledAlready)
        {
            emit checkCompleted(false, tr("Unknown error when fetching data "
                "from Google Reader. Could be a network problem."));
        }
    }
    else
    {
        d->buffer->seek(0);
        d->receivedData = d->buffer->readAll();
        d->buffer->close();
        delete d->buffer;
        d->buffer = 0;
        qDebug() << "greader(api)/unread-count: Got the unread count data.";
        QTimer::singleShot(0, this, SLOT(handleData()));
    }
}

void UnreadItemsCheck::readResponseHeader(const QHttpResponseHeader &header)
{
#ifdef VERBOSE_NETWORK_DATA_DEBUG
    qDebug() << "greader(api)/unread-count: Got a response header: "
             << header.toString();
#endif
    if (header.statusCode() != 200) // if error/unexpected
    {
        // clear our possible SID as it's invalid anyway
        // TODO login and then start a new unread count check?
        if (header.statusCode() == 401) d->loginData.setSid("");

        emit checkCompleted(false, tr("Got an error or unexpected response "
            "(%1) from Google Reader.").arg(header.statusCode() ) );
        d->requestErrorHandledAlready = true;
    }
}

void UnreadItemsCheck::notifyAboutUnsupportedIdentifier(const QString &identifier)
{
    qCritical() << "greader(api)/unread-count: "
                  "An unidentified identifier found: " << identifier <<
                  ". This application doesn't support this "
                  "identifier.";
    // TODO support some other identifiers and perhaps
    // code a way to let the use know if an unsupported
    // identifier has been found
}
